Advanced Setup¶
This section describes the steps to install and setup LiuAlgoTrader for first time use on a pre-existing PostgreSQL database installation. The section is mostly relevant for users that did not go through the Quickstart wizard.
Prerequisite¶
- Paper, or a funded Live account with Alpaca Markets ,
- An Installed and configured PostgreSQL database.
Data Sources¶
LiuAlgoTrader supports both Alpaca and Polygon.io as data-sources, however Alpaca data offering has a more limited functionality.
When using Polygon.io A Stater, or above subscription w/ Polygon is required.
Alpaca is the default data-provider, Unlimited/Pro subscription is recommanded.
Database Setup¶
LiuAlgoTrader applications persist trade details to a relational database. The applications are developed on-top of PostgreSQL, however the code may be adopted to use any SQL-supported database .
The database is mostly used for storing trade details, including stock name, price, quantity, indicators and more. This data is valuable when analysing the trading day, and improving the algorithmic strategies. For further reading, see Understanding the Data Model.
Database Connection¶
The DSN environment variable holds the connection string to the database. For example:
export DSN="postgresql://momentum@localhost/tradedb"
To learn more on how to select your connection string.
Database Schema script¶
The following SQL script creates the required database schema:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | CREATE TYPE trade_operation AS ENUM ('buy', 'sell'); CREATE TYPE trade_env AS ENUM ('PAPER', 'PROD'); CREATE SEQUENCE IF NOT EXISTS transaction_id_seq START 1; CREATE TABLE IF NOT EXISTS algo_run ( algo_run_id serial PRIMARY KEY, algo_name text NOT NULL, algo_env text NOT NULL, build_number text NOT NULL, parameters jsonb, start_time timestamp DEFAULT current_timestamp, end_time timestamp, end_reason text ); CREATE TABLE IF NOT EXISTS trades ( trade_id serial PRIMARY KEY, algo_run_id integer REFERENCES algo_run(algo_run_id), is_win bool, symbol text NOT NULL, qty integer NOT NULL check (qty > 0), buy_price decimal (8, 2) NOT NULL, buy_indicators jsonb NOT NULL, buy_time timestamp DEFAULT current_timestamp, sell_price decimal (8, 2), sell_indicators jsonb, sell_time timestamp, client_sell_time text, client_buy_time text ); CREATE INDEX ON trades(symbol); CREATE INDEX ON trades(algo_run_id); CREATE INDEX ON trades(is_win); CREATE TABLE IF NOT EXISTS new_trades ( trade_id serial PRIMARY KEY, algo_run_id integer REFERENCES algo_run(algo_run_id), symbol text NOT NULL, operation trade_operation NOT NULL, qty integer NOT NULL check (qty > 0), price decimal (8, 2) NOT NULL, indicators jsonb NOT NULL, client_time text, tstamp timestamp DEFAULT current_timestamp ); CREATE INDEX ON new_trades(symbol); CREATE INDEX ON new_trades(algo_run_id); ALTER TABLE new_trades ADD COLUMN stop_price decimal (8, 2); ALTER TABLE new_trades ADD COLUMN target_price decimal (8, 2); ALTER TYPE trade_operation ADD VALUE 'sell_short'; ALTER TYPE trade_operation ADD VALUE 'buy_short'; CREATE TABLE IF NOT EXISTS ticker_data ( symbol text PRIMARY KEY, name text NOT NULL, description text NOT NULL, tags text [ ], similar_tickers text [ ], industry text, sector text, exchange text, short_ratio float, create_tstamp timestamp DEFAULT current_timestamp, modify_tstamp timestamp ); CREATE INDEX ON ticker_data(sector); CREATE INDEX ON ticker_data(industry); CREATE INDEX ON ticker_data(tags); CREATE INDEX ON ticker_data(similar_tickers); ALTER TABLE algo_run ADD COLUMN batch_id text NOT NULL DEFAULT ''; CREATE INDEX ON algo_run(batch_id); ALTER TABLE algo_run ADD COLUMN ref_algo_run integer REFERENCES algo_run(algo_run_id); ALTER TABLE new_trades ADD COLUMN expire_tstamp timestamp; CREATE TABLE IF NOT EXISTS trending_tickers ( trending_id serial PRIMARY KEY, batch_id text NOT NULL, symbol text NOT NULL, create_tstamp timestamp DEFAULT current_timestamp ); CREATE INDEX ON trending_tickers(batch_id); INSERT INTO trending_tickers (symbol, batch_id) SELECT distinct t.symbol, r.batch_id FROM new_trades AS t, algo_run AS r WHERE t.algo_run_id = r.algo_run_id AND batch_id != ''; BEGIN ; alter TABLE new_trades DROP constraint "new_trades_qty_check"; alter TABLE new_trades add check (qty != 0); COMMIT; CREATE TABLE IF NOT EXISTS stock_ohlc ( symbol_id serial PRIMARY KEY, symbol text NOT NULL, symbol_date date NOT NULL, open float NOT NULL, high float NOT NULL, low float NOT NULL, close float NOT NULL, volume int NOT NULL, indicators JSONB, modify_tstamp timestamp, create_tstamp timestamp DEFAULT current_timestamp, UNIQUE(symbol, symbol_date) ); CREATE INDEX ON stock_ohlc(symbol); CREATE INDEX ON stock_ohlc(symbol_date); CREATE TABLE IF NOT EXISTS gain_loss ( gain_loss_id serial PRIMARY KEY, symbol text NOT NULL, algo_run_id integer NOT NULL REFERENCES algo_run(algo_run_id), gain_percentage decimal (5, 2) NOT NULL, gain_value decimal (8, 2) NOT NULL, tstamp timestamp DEFAULT current_timestamp, UNIQUE(symbol, algo_run_id) ); CREATE INDEX ON gain_loss(symbol, algo_run_id); CREATE INDEX ON algo_run(start_time); CREATE INDEX ON new_trades(tstamp); ALTER TABLE new_trades ALTER COLUMN algo_run_id SET NOT NULL; CREATE TABLE IF NOT EXISTS trade_analysis ( trade_analysis_id serial PRIMARY KEY, symbol text NOT NULL, algo_run_id integer NOT NULL REFERENCES algo_run(algo_run_id), start_tstamp timestamp with time zone NOT NULL, end_tstamp timestamp with time zone NOT NULL, gain_percentage decimal (5, 2) NOT NULL, gain_value decimal (8, 2) NOT NULL, r_units decimal(4, 2), tstamp timestamp with time zone DEFAULT current_timestamp, UNIQUE(symbol, algo_run_id, start_tstamp) ); --------------- -- Portfolio -- --------------- alter TABLE new_trades DROP constraint "new_trades_qty_check"; CREATE TABLE IF NOT EXISTS keystore ( keystore_id serial PRIMARY KEY, algo_name text NOT NULL, context text NOT NULL, key text NOT NULL, value text NOT NULL, tstamp timestamp with time zone DEFAULT current_timestamp, CONSTRAINT ukey UNIQUE (algo_name, key, context) ); CREATE INDEX ON keystore(algo_name); CREATE INDEX ON keystore(algo_name, key, context); CREATE TABLE IF NOT EXISTS portfolio ( portfolio_id text PRIMARY KEY, size int NOT NULL, stock_count int NOT NULL, parameters JSONB, expire_tstamp timestamp with time zone, tstamp timestamp with time zone DEFAULT current_timestamp ); CREATE TABLE IF NOT EXISTS portfolio_batch_ids ( assoc_id serial PRIMARY KEY, portfolio_id text NOT NULL REFERENCES portfolio(portfolio_id), batch_id text NOT NULL, UNIQUE(portfolio_id, batch_id) ); CREATE INDEX ON portfolio_batch_ids(batch_id); CREATE INDEX ON portfolio_batch_ids(portfolio_id); -------------- -- Accounts -- -------------- CREATE TABLE IF NOT EXISTS accounts ( account_id serial PRIMARY KEY, balance float NOT NULL DEFAULT 0.0, allow_negative bool NOT NULL DEFAULT FALSE, credit_line float NOT NULL DEFAULT 0. CHECK (credit_line > 0 or not allow_negative ), details JSONB, create_tstamp timestamp with time zone DEFAULT current_timestamp, modified_tstamp timestamp with time zone ); CREATE TABLE IF NOT EXISTS account_transactions ( transaction_id serial PRIMARY KEY, account_id SERIAL REFERENCES accounts(account_id), amount float NOT NULL, details JSONB, tstamp timestamp with time zone DEFAULT current_timestamp ); ALTER TABLE account_transactions ADD COLUMN create_tstamp timestamp with time zone DEFAULT current_timestamp; CREATE OR REPLACE FUNCTION update_balance() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$ DECLARE negative bool; b float; credit float; BEGIN SELECT allow_negative, balance, credit_line FROM accounts WHERE account_id = NEW .account_id INTO negative, b, credit; UPDATE accounts SET balance = balance + NEW .amount, modified_tstamp = 'now()' WHERE account_id = NEW .account_id; RETURN NEW; END; $$; CREATE TRIGGER balance_update BEFORE INSERT OR UPDATE ON account_transactions FOR EACH ROW EXECUTE PROCEDURE update_balance(); ALTER TABLE portfolio DROP COLUMN stock_count; ALTER TABLE portfolio ADD COLUMN account_id int REFERENCES accounts(account_id); CREATE INDEX IF NOT EXISTS portfolio_idx ON portfolio(account_id); ALTER TABLE trending_tickers ADD COLUMN scanner_name text NOT NULL DEFAULT 'momentum'; CREATE INDEX IF NOT EXISTS trending_tickers_scanner_idx ON trending_tickers (scanner_name); CREATE INDEX IF NOT EXISTS trending_tickers_tstamp_idx ON trending_tickers (create_tstamp); ALTER TABLE trending_tickers ALTER COLUMN create_tstamp TYPE timestamp with time zone; CREATE TABLE IF NOT EXISTS optimizer_run ( optimizer_run_id serial PRIMARY KEY, optimizer_session_id text NOT NULL, batch_id text NOT NULL, tstamp timestamp with time zone DEFAULT current_timestamp ); CREATE INDEX IF NOT EXISTS optimizer_run_idx ON optimizer_run(optimizer_session_id); ALTER TABLE optimizer_run ADD COLUMN parameters text NOT NULL DEFAULT(''); alter table new_trades alter COLUMN qty type numeric(24,10) using cast(qty as numeric); ALTER TABLE new_trades ADD COLUMN trade_fee NUMERIC(9,2) NOT NULL DEFAULT 0.0; CREATE TYPE asset_type AS ENUM ('US_EQUITIES', 'CRYPTO'); ALTER TABLE portfolio ADD COLUMN assets asset_type NOT NULL DEFAULT 'US_EQUITIES'; ALTER TABLE portfolio ADD COLUMN external_account_id text; ALTER TABLE portfolio ADD COLUMN broker text; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE IF NOT EXISTS trade_plan ( trade_plan_id uuid DEFAULT uuid_generate_v4 (), portfolio_id text NOT NULL REFERENCES portfolio(portfolio_id), filename text NOT NULL, start_date date NOT NULL, strategy_name text NOT NULL, parameters JSONB, create_tstamp timestamp with time zone DEFAULT current_timestamp, modify_tstamp timestamp with time zone, expire_tstamp timestamp with time zone, PRIMARY KEY (trade_plan_id) ); CREATE FUNCTION update_modify_tstamp_action() RETURNS TRIGGER AS $$ BEGIN NEW.modify_tstamp = now(); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_trade_plan_modify_tstamp BEFORE UPDATE ON trade_plan FOR EACH ROW EXECUTE PROCEDURE update_modify_tstamp_action(); CREATE TABLE IF NOT EXISTS trade_plan_execution_audit ( execution_id serial PRIMARY KEY, trade_plan_id uuid REFERENCES trade_plan(trade_plan_id), details JSONB, started_on timestamp with time zone DEFAULT current_timestamp, ended_on timestamp with time zone DEFAULT current_timestamp ); ALTER TABLE trade_plan DROP COLUMN filename; alter table keystore drop column context; alter table keystore drop column algo_name; drop table keystore; CREATE TABLE IF NOT EXISTS keystore ( key text PRIMARY KEY, value text NOT NULL, tstamp timestamp with time zone DEFAULT current_timestamp ); alter table portfolio_batch_ids drop CONSTRAINT "portfolio_batch_ids_portfolio_id_batch_id_key"; |
NOTE: When updating from a predecessor version of LiuAlgoTrader, it is recommended to re-run the database script. the latest schema file can be found in the database folder in LiuAlgoTrader distribution. The script is backward compatible and executing the scripts will automatically migrate the data to the latest version.
Data Providers¶
Algorithmic trading is only as good as the data feed it rely on. Without an accurate, near-real-time data feed even the best strategy will keep losing money.
LiuAlgoTrader currently support Polygon.io as a data-provider, as well as Alapaca native data integration. Additional integration are underway (Finnhub).
Example Setup on GCP¶
Alpaca Markets seems to be hosted on GCP us-east4-c, while Polygon.io is hosted in Equinix. It is a good idea to locate the trading servers close to the brokerage servers to best execution times.
While you may run LiuAlgoTrader on your home desktop or laptop, having a hosted server, even a lean one could help the consistency of your trades.
The steps for basic hosting on Google Cloud Platform:
1. In the SQL section create an instance of PostgreSQL, this would be a fully hosted service. Minimal configuration should be enough to start with, though high-availability & daily backup of the database is recommended.
2. In the Compute Engine create a VM Instance, the smallest (g1-small) should be OK to start with. Even though its a single CPU, running multiple LiuAlgo Trader processes would be able to trade > 400 concurrent stock without saturating the computation resources.
3. Install locally the gcloud CLI, connect to your remote instance create a folder to contain your credentials for connecting with other GCP services, as well as install your database private keys.
4. Connect to the database, and run the schema script. If you’re developing remotely, make sure to install the tunnel to your database from your local machine.
You’re now ready to run LiuAlgoTrader in the cloud. Your total monthly bill should be around $70/month while consuming the logging services.